#include <iostream>
#include "osm_converter.h"
#include "makecells.h"

#include <expat.h>
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <iosfwd>
#include <sstream>
#include <fstream>
#include <boost/lexical_cast.hpp>

void startTag(void * data, char const * el, char const ** attr)
{
    OsmConverter * converter = (OsmConverter *) data;
    int i = 0;
    if (std::string(el) == "node")
    {
        converter->processNode(attr);
    }
    else if (std::string(el) == "way")
    {
        converter->way_started_ = true;
    }
    else if (std::string(el) == "nd" && converter->way_started_)
    {
        converter->processNd(attr);
    }
    else if (std::string(el) == "tag" && converter->way_started_)
    {
        converter->processTag(attr);
    }
}

void endTag(void * data, char const * el)
{
    OsmConverter * converter = (OsmConverter *) data;
    std::string el_str(el);
    if(el_str == "way")
    {
        converter->endOfWay();
    }
}

OsmConverter::OsmConverter() 
{
}

OsmConverter::OsmConverter(std::string const & file) : goal_("highway"), way_started_(false)
{
    parse(file);
}

ExtractedItem const & OsmConverter::operator[] (size_t idx) const
{
    return items_[idx];
}

size_t OsmConverter::size() const
{
	return items_.size();
}

void OsmConverter::parse(std::string const & file)
{
    std::ifstream f(file.c_str());
    XML_Parser parser = XML_ParserCreate("UTF-8");
    XML_SetElementHandler(parser, startTag, endTag);
    XML_SetUserData(parser, this);

    while (!f.eof())
    {
        std::string buffer;
        std::getline(f, buffer);
        XML_Parse(parser, buffer.c_str(), buffer.size(), 0);
    }

    f.close();
    XML_ParserFree(parser);
}

void OsmConverter::processNode(char const ** attr)
{
    int i = 0;
    point_t point;
    OsmConverter::node_id_t id = 0;

    while (attr[i])
    {
        std::string name(attr[i]);
        std::string val(attr[i + 1]);
        if (name == "lat")
        {
            point.x = boost::lexical_cast<double>(val);
        }
        else if (name == "lon")
        {
            point.y = boost::lexical_cast<double>(val);
        }
        else if (name == "id")
        {
            id = boost::lexical_cast<OsmConverter::node_id_t > (val);
        }
        i += 2;
    }
    
    node_map_.insert(std::make_pair(id, point));
}

void OsmConverter::processNd(char const ** attr)
{
    int i = 0;
    while (attr[i])
    {
        std::string name(attr[i]);
        std::string val(attr[i + 1]);
        if (name == "ref")
        {
            current_way_.push_back(boost::lexical_cast<node_id_t>(val));
            break;
        }
        i += 2;
    }
}

void OsmConverter::processTag(char const ** attr)
{
    int i = 0;
    std::pair<std::string, std::string> pair;
    while (attr[i])
    {
        std::string name(attr[i]);
        std::string val(attr[i + 1]);
        if (name == "k")
        {
            pair.first = val;
        }
        else if (name == "v")
        {
            pair.second = val;
        }
        i += 2;
    }
    attributs_.insert(pair);
}

void OsmConverter::endOfWay()
{
    std::string type = getType();
	if (type != "")
    {
		boost::unordered_map<std::string, std::string>::iterator it = attributs_.find(std::string("name"));
		std::string title; 
		if (it != attributs_.end())
			title = it->second;
		
		way_t way;
		for(size_t j = 0; j < current_way_.size(); ++j)
		{
			node_map_t::const_iterator it = node_map_.find(current_way_[j]);
			if (it != node_map_.end())
				way.push_back(it->second);
		}

		items_.push_back(ExtractedItem(way, title, type));
    }
    current_way_.clear();
    way_started_ = false;
    attributs_.clear();
}

std::string OsmConverter::getType() const
{
	std::vector<std::string> values;
	values.push_back("highway");
	values.push_back("waterway");
	values.push_back("landuse");
	values.push_back("water");

	for (size_t i = 0; i < values.size(); ++i)
	{
		if (attributs_.find(values[i]) != attributs_.end())
			return values[i];
	}
	return "";
}

polygon_t get_polygon(way_t const & way, bool is_area)
{
	way_t result = way;
	if (!is_area)
	{
		way_t r_copy(way.size(), point_t());
		std::reverse_copy(way.begin(), way.end(), r_copy.begin());
		
		for (way_t::iterator it = r_copy.begin(); it != r_copy.end(); ++it)
		{
			it->y -= 0.0001;
		}

		result.insert(result.end(), r_copy.begin(), r_copy.end());
	}
    
	for(way_t::iterator it = result.begin(); it != result.end(); ++it)
	{
		(*it) *= 1000;
	}

    polygon_t polygon;
    polygon.add(contour_t(result.begin(), result.end()));
    return polygon;
}

ExtractedItem::ExtractedItem()
{
}

ExtractedItem::ExtractedItem(way_t const & way, std::string const & title, std::string const & type)
	: way_(way), title_(title), type_(type)
{
}

way_t const & ExtractedItem::way() const
{
	return way_;
}

std::string const & ExtractedItem::title() const
{
	return title_;
}

bool ExtractedItem::is_area() const
{
	return (type_ == "area" || type_ == "landuse" || type_ == "green"); 
}

mobj_type ExtractedItem::type() const
{
	if (type_ == "waterway")
		return river;
	if (type_ == "landuse")
		return green;
	if (type_ == "water")
		return lake;
	if (type_ == "highway")
		return road;
	return smth;
}

DBMaster extractData(OsmConverter const & converter)
{
	DBMaster data;
	CELL_DATA cell;
	cell.cell_id = std::make_pair(1, 1);
    
	for (int i = 0; i < converter.size(); ++i)
    {
		MAP_OBJECT object;
		ExtractedItem item = converter[i];
		strcpy(object.label, item.title().c_str());
		object.polygon = get_polygon(item.way(), item.is_area());
		object.mobj_id = i;
		object.mtype = item.type();
		data.map_objects.insert(std::make_pair(object.mobj_id, object));
		cell.objects.push_back(i);
    }
	
	data.cells = make_cells(data.map_objects, 1000);
	return data;
}